﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace NASA.BeAMartian.Controls
{
    /// <summary>
    /// The EllipticalLayout lays out it's children on the border of 
    /// an ellipse whose a is this.Width / 2 and b is this.Height / 2.
    /// </summary>
    public class EllipticalLayout : Panel
    {
        /// <summary>
        /// A value indicating whether a dependency property change handler
        /// should ignore the next change notification.  This is used to reset
        /// the value of properties without performing any of the actions in
        /// their change handlers.
        /// </summary>
        private static bool _ignorePropertyChange;

        /// <summary>
        /// Initializes a new instance of the WrapPanel class.
        /// </summary>
        public EllipticalLayout()
        {
            this.SizeChanged += new SizeChangedEventHandler(EllipticalLayout_SizeChanged);
        }

        void EllipticalLayout_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            this.InvalidateArrange();
        }

        #region public attached Angle
        /// <summary>
        /// Gets the value of the Angle attached property for the specified 
        /// element.
        /// </summary>
        /// <param name="element">
        /// The element from which the property value is read.
        /// </param>
        /// <returns>The Angle property value for the element.</returns>
        /// <remarks>
        /// The GetAngle and SetAngle methods enable you to get and set the 
        /// value of the Angle property for an element in code.
        /// </remarks>
        public static double GetAngle(UIElement element)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }
            return (double)element.GetValue(AngleProperty);
        }

        /// <summary>
        /// Sets the value of the Angle attached property for the specified 
        /// element to the specified Point value.
        /// </summary>
        /// <param name="element">
        /// The element to which the attached property is assigned.
        /// </param>
        /// <param name="dock">
        /// The dock value to assign to the specified element.
        /// </param>
        /// <remarks>
        /// The GetAngle and SetAngle methods enable you to get and set the 
        /// value of the Angle property for an element in code.
        /// </remarks>
        public static void SetAngle(UIElement element, double angle)
        {
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }
            element.SetValue(AngleProperty, angle);
        }

        /// <summary>
        /// Identifies the Angle (in degrees) dependency property.
        /// </summary>
        public static readonly DependencyProperty AngleProperty =
            DependencyProperty.RegisterAttached(
                "Angle",
                typeof(double),
                typeof(EllipticalLayout),
                new PropertyMetadata(0.0, OnAnglePropertyChanged));

        /// <summary>
        /// AngleProperty property changed handler.
        /// </summary>
        /// <param name="d">UIElement that changed its Angle.</param>
        /// <param name="e">Event arguments.</param>
        private static void OnAnglePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            // Ignore the change if requested
            if (_ignorePropertyChange)
            {
                _ignorePropertyChange = false;
                return;
            }

            EllipticalLayout panel = VisualTreeHelper.GetParent(d) as EllipticalLayout;

            if (panel != null)
            {
                panel.InvalidateMeasure();
            }
        }
        #endregion public attached Angle

        /// <summary>
        /// Measures the child elements of a FittedCanvas in preparation for 
        /// arranging them during the ArrangeOverride pass.
        /// </summary>
        /// <param name="constraint">
        /// The area available to the FittedCanvas.
        /// </param>
        /// <returns>The desired size of the FittedCanvas.</returns>
        /// <remarks>
        /// The MeasureOverride method measures each child element in the 
        /// FittedCanvas and returns constraint, or the combined size of all of 
        /// the elements in the FittedCanvas, depending on which is larger. 
        /// </remarks>
        protected override Size MeasureOverride(Size constraint)
        {
            //double maximumWidth = 0.0;
            //double maximumHeight = 0.0;

            // Measure each of the Children
            foreach (UIElement element in Children)
            {
                if (element.Visibility != Visibility.Collapsed)
                {
                    element.Measure(new Size(double.MaxValue, double.MaxValue));
                    //Size desiredSize = element.DesiredSize;
                    //double angle = GetAngle(element);
                    //maximumHeight = Math.Max(maximumHeight, position.Y + desiredSize.Height);
                    //maximumWidth = Math.Max(maximumWidth, position.X + desiredSize.Width);
                }
            }

            return constraint;
        }

        /// <summary>
        /// Arranges the child elements of the FittedCanvas control.
        /// </summary>
        /// <param name="arrangeSize">
        /// The area in the parent element that the FittedCanvas should use to 
        /// arrange its child elements.
        /// </param>
        /// <returns>
        /// The actual size of the EllipticalLayout after the child elements are 
        /// arranged. The actual size should always equal arrangeSize.
        /// </returns>
        /// <remarks>
        /// If child elements are added that exceed arrangeSize, 
        /// the elements will be truncated. The ArrangeOverride method is 
        /// called during the second layout pass of the control. 
        /// </remarks>
        protected override Size ArrangeOverride(Size arrangeSize)
        {
            if (this.ActualWidth == 0.0 || this.ActualHeight == 0.0)
                return arrangeSize;

            foreach (UIElement element in this.Children)
            {
                double angleDegrees = GetAngle(element);

                // Normalize to positive angle between 0 and 360 inclusive.
                if (angleDegrees > 360.0)
                {
                    angleDegrees -= (Math.Floor(angleDegrees / 360.0) * 360.0);
                }
                else if (angleDegrees < 0)
                {
                    angleDegrees = -angleDegrees;
                    if (angleDegrees > 360.0) angleDegrees -= (Math.Floor(angleDegrees / 360.0) * 360.0);
                    angleDegrees = 360 - angleDegrees;
                }

                double angleRadians = -angleDegrees * (Math.PI / 180.0);
                Size desiredSize = element.DesiredSize;

                double asq = this.ActualWidth / 2.0;
                asq *= asq;
                double bsq = this.ActualHeight / 2.0;
                bsq *= bsq;
                double msq = Math.Tan(angleRadians);
                msq *= msq;

                double x = Math.Sqrt(1.0 / (1.0 / asq + msq / bsq));
                if (angleDegrees > 90 && angleDegrees < 270) x = -x;
                x += this.ActualWidth / 2.0;

                double y;
                if(msq == 0.0)
                    y = 0.0;
                else
                    y = Math.Sqrt(1.0 / (1.0 / (msq * asq) + 1.0 / bsq));

                if (angleDegrees > 180 && angleDegrees < 360) y = -y;
                y += this.ActualHeight / 2.0;

                element.Arrange(new Rect(x, y, desiredSize.Width, desiredSize.Height));
            }

            return arrangeSize;
        }
    }
}
